/*
 *   Copyright (C) 2014  Raghavan Santhanam, raghavanil4m@gmail.com.
 *   I wrote this cross-platform talk tool, WindowsLinuxTalk, for the
 *   love of programming entirely under Ubuntu(Linux) for Windows in
 *   a remote fashion. Send any opinions/suggestions/bug reports to
 *   raghavanil4m@gmail.com.
 *
 *   LinuxIssue.c is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   LinuxIssue.c is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* LinuxIssue.c -- Classic socket client that sends commands to Windows
 * server, intended to be executed under Windows and gets back the streamed(sent)
 * execution output(stdout and stderr) along with the exit code of the command
 * executed. */

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <setjmp.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>

#include <sys/socket.h>
#include <arpa/inet.h>

//#define __DEBUG__

#ifdef __DEBUG__
#	define LOG(...) printf(__VA_ARGS__)
#else
#	define LOG(...)
#endif

static int LinuxClientSockfd = -1;
static struct sockaddr_in WindowsServAddrPort;
static const int WinPort = 7000;
static bool Connected;
static bool Received;
static bool Issued;

static int LinuxClientKillSockfd = -1;
static struct sockaddr_in WindowsServAddrKillPort;
static const int WinKillPort = 7001;
static bool KillConnected;

static char WinIp[sizeof("xxx:xxx:xxx:xxx")] = "0.0.0.0";
static const char *ConfigFile = "./ConfigFile.txt";

static jmp_buf SaneState;

static void PrintProgName(char *n)
{
	const char *progName = n;
	char p[strlen(progName) + 1];
	memset(p, 0, sizeof(p));
	strcpy(p, progName);

	char *shortenName = p + strlen(p) - 1;
	while (shortenName >= p && *shortenName != '/') {
		shortenName--;
	}
	if (shortenName < p || *shortenName == '/') {
		shortenName++;
	}

	printf("\n\n ** %s **\n\n", shortenName);
	printf("Usage : %s [WindowsCmd]\n\n", shortenName);
	printf("NOTE: Windows IP address will be read from %s\n\n", ConfigFile);
}

static void CloseConnection(void)
{
	if (LinuxClientSockfd != -1) {
		close(LinuxClientSockfd);
		LinuxClientSockfd = -1;
	}
}

static void CloseKillConnection(void)
{
	if (LinuxClientKillSockfd != -1) {
		close(LinuxClientKillSockfd);
		LinuxClientKillSockfd = -1;
	}
}

static void CatchSignal(int sig)
{
	LOG("Caught : ");

	switch (sig) {
	case SIGSEGV:	LOG("** SIGSEGV **");
			exit(0);
			break;
	case SIGINT:	LOG("SIGINT\n");
			if (!Issued) {
				LOG("Not issued.\n");
				if (Connected) {
					LOG("Connected.\n");
					(void)sendto(LinuxClientSockfd, "exit", sizeof("exit") - 1, 0, NULL, 0);
				}
			} else {
				LOG("Issued.\n");
				if (KillConnected) {
					LOG("Kill Connected.\n");
					(void)sendto(LinuxClientKillSockfd, "stop", sizeof("stop") - 1, 0, NULL, 0);
				}
			}
			CloseConnection();
			CloseKillConnection();
			exit(0);
			return;

			break;
	case SIGTERM: 	LOG("SIGTERM");
			exit(0);
			break;
	case SIGPIPE:	LOG("SIGPIPE");
			if (LinuxClientSockfd != -1 ) {
				if (Connected) {
					(void)sendto(LinuxClientSockfd, "exit", sizeof("exit") - 1, 0, NULL, 0);
				}
				close(LinuxClientSockfd);
				LinuxClientSockfd = -1;
			}
			exit(0);
			break;
	default:	LOG("Unknown/Some other signal(%d)", sig);
			exit(0);
			break;
	}

	LOG("\n");

	longjmp(SaneState, 1);
}

static void RegisterSignalHandlers(void)
{
	//LOG("Registering signal handlers . . .\n");

	(void)signal(SIGSEGV, CatchSignal);
	(void)signal(SIGINT, CatchSignal);
	(void)signal(SIGTERM, CatchSignal);
	(void)signal(SIGPIPE, CatchSignal);

	//LOG("Registered.\n");
}

static void SaveState(void)
{
	//LOG("Saving state . . .\n");

	bool firstTime = setjmp(SaneState) == 0;
	if (firstTime) {
		//LOG("Saved.\n");
	} else {
		printf("Seems something wrong happened. Restoring to previous state . . .\n");
		RegisterSignalHandlers();
	}
}

static bool PrepareForIssuingCmds(void)
{
	bool prepared = false;

	//LOG("Preparing for issuing commands to Windows . . .\n");

	FILE *fp = fopen(ConfigFile, "r");
	bool fopened = fp != NULL;
	if (!fopened) {
		printf("Error: failed to fopen - %s\n", strerror(errno));
		goto finish;
	}
	bool read = fscanf(fp, "%s", WinIp) == 1;
	if (!read) {
		printf("Error: failed to read - %s\n", strerror(errno));
		goto finish;
	}
	fclose(fp);
	fp = NULL;

	if (LinuxClientSockfd != -1) {
		close(LinuxClientSockfd);
		LinuxClientSockfd = -1;
	}

	LinuxClientSockfd = socket(AF_INET, SOCK_STREAM, 0);
	bool opened = LinuxClientSockfd != -1;
	if (!opened) {
		printf("Error: failed to open socket - %s\n", strerror(errno));
		goto finish;
	}

	WindowsServAddrPort.sin_family = AF_INET;
	WindowsServAddrPort.sin_port = htons(WinPort);

	int af = AF_INET;
	int convIp = inet_pton(af, WinIp, &WindowsServAddrPort.sin_addr);
	bool invalidIp = convIp == 0;
	bool invalidAfFam = convIp == -1;
	if (invalidIp) {
		printf("Error: Invalid ip(%s)\n", WinIp);
		goto finish;
	} else if (invalidAfFam) {
		printf("Error: Invalid AF(%d) family\n", af);
		goto finish;
	}

	//LOG("Connecting . . .\n");

	Connected = false;

	bool hooked = connect(LinuxClientSockfd, (struct sockaddr *)&WindowsServAddrPort, sizeof(struct sockaddr)) != -1;
	if (!hooked) {
		printf("Error: failed to connect - %s\n", strerror(errno));
		goto finish;
	}

	Connected = true;

	prepared = true;

	//LOG("Prepared.\n");
	//LOG("Ready.\n\n");

finish:

	if (!prepared) {
		CloseConnection();
	}

	return prepared;
}

static bool PrepareForKillingCmds(void)
{
	bool prepared = false;

	LOG("Preparing for killing commands given to Windows . . .\n");

	FILE *fp = fopen(ConfigFile, "r");
	bool fopened = fp != NULL;
	if (!fopened) {
		printf("Error: failed to fopen - %s\n", strerror(errno));
		goto finish;
	}
	bool read = fscanf(fp, "%s", WinIp) == 1;
	if (!read) {
		printf("Error: failed to read - %s\n", strerror(errno));
		goto finish;
	}
	fclose(fp);
	fp = NULL;

	if (LinuxClientKillSockfd != -1) {
		close(LinuxClientKillSockfd);
		LinuxClientKillSockfd = -1;
	}

	LinuxClientKillSockfd = socket(AF_INET, SOCK_STREAM, 0);
	bool opened = LinuxClientKillSockfd != -1;
	if (!opened) {
		printf("Error: failed to open socket - %s\n", strerror(errno));
		goto finish;
	}

	WindowsServAddrKillPort.sin_family = AF_INET;
	WindowsServAddrKillPort.sin_port = htons(WinKillPort);

	int af = AF_INET;
	int convIp = inet_pton(af, WinIp, &WindowsServAddrKillPort.sin_addr);
	bool invalidIp = convIp == 0;
	bool invalidAfFam = convIp == -1;
	if (invalidIp) {
		printf("Error: Invalid ip(%s)\n", WinIp);
		goto finish;
	} else if (invalidAfFam) {
		printf("Error: Invalid AF(%d) family\n", af);
		goto finish;
	}

	LOG("Connecting . . .\n");

	KillConnected = false;

	bool hooked = connect(LinuxClientKillSockfd, (struct sockaddr *)&WindowsServAddrKillPort, sizeof(struct sockaddr)) != -1;
	if (!hooked) {
		printf("Error: failed to connect - %s\n", strerror(errno));
		goto finish;
	}

	KillConnected = true;

	prepared = true;

	LOG("Prepared.\n");
	LOG("Ready.\n\n");

finish:

	if (!prepared) {
		CloseKillConnection();
	}

	return prepared;
}

static bool IssueCmds(const char *WindowsCommand)
{
	bool issued = false;

	// LOG("\n\nIssuing commands . . .\n");

	printf("cmd> ");
	const size_t cmdSize = 1024;
	char cmd[cmdSize];
	memset(cmd, 0, sizeof(cmd));

	if (WindowsCommand) {
		strcpy(cmd, WindowsCommand);
	} else {
		bool yes = fscanf(stdin, "%[^\n]", cmd) == 1;
		fgetc(stdin);
		if (!yes && errno) {
			printf("Error: failed to read command - %s\n", strerror(errno));
			goto bye;
		}
	}

	char *removeLeadSpace = cmd;
	while (*removeLeadSpace && *removeLeadSpace == ' ') {
		removeLeadSpace++;
	}

	bool stringLeft = *removeLeadSpace;

	char *removeTrailSpace = NULL;
	if (stringLeft) {
		removeTrailSpace = cmd + strlen(cmd) - 1;

		char *savedEnd = removeTrailSpace;
		while (removeTrailSpace > cmd && *removeTrailSpace == ' ') {
			removeTrailSpace--;
		}
		if (removeTrailSpace != savedEnd) {
			removeTrailSpace[1] = '\0';
		}
	}

	bool anythingToBeSent = *removeLeadSpace;

	if (anythingToBeSent) {
		char *cmdToBeSent = removeLeadSpace;

		LOG("Sending (%s)\n", cmdToBeSent);

		Issued = false;

		ssize_t bytesSent = sendto(LinuxClientSockfd, cmdToBeSent, strlen(cmdToBeSent), 0, NULL, 0);
		bool sent = bytesSent != -1;
		if (!sent) {
			printf("Error: failed to send - %s\n", strerror(errno));
			goto bye;
		}

		Issued = true;

		if (!strcmp(cmd, "exit")) {
			exit(0);
		}

		Received = false;

		printf("\n\n----------------------------------------------------------\n\n");

		while (1) {
			char b[1024] = "";
			ssize_t bytesReceived = recvfrom(LinuxClientSockfd, b, sizeof(b) - 1, 0, NULL, 0);
			bool err = bytesReceived == -1;
			if (err) {
				printf("Error: failed to recvfrom - %s\n", strerror(errno));
				goto bye;
			}

			Received = true;
	
			char *endStr = strstr(b, "$$ end(");
			bool endLoop = endStr;
			long int exitCode = -1;

			if (endLoop) {
				// Leave end string.
				char *exitCodeStr = endStr + strlen("$$ end(");
				char *closingParen = strchr(exitCodeStr, ')');
				if (closingParen) {
					*closingParen = '\0';
				}
				(void)sscanf(exitCodeStr, "%ld", &exitCode);	

				*endStr = '\0';					
			}

			// Common to both end-str and non-end str case.
			printf("%s", b);
			fflush(stdout);

			if (endLoop) {
				if (exitCode) {
					printf("\nCommand (likely) failed.\n");
				}

				LOG("\n\nEnding loop!\n\n");
				break;
			}

			usleep(1);
		}

		Received = false;

		usleep(1);
	} else {
		LOG("Nothing to be sent.\n");
	}

	printf("\n\n----------------------------------------------------------\n\n");

	issued = true;

	LOG("\n\nIssued.\n");

bye:
	return issued;	
}

int main(int argc, char *argv[])
{
	PrintProgName(argv[0]);

	SaveState();

	RegisterSignalHandlers();

	bool killPrepared = false;
	while (!killPrepared) {
		killPrepared = PrepareForKillingCmds();
		if (!killPrepared) {
			printf("Error: failed to kill-prepare.\n");
		}
	}

	bool interactive = argc == 1;

	if (interactive) {
		while (1) {
			bool prepared = false;
			while (!prepared) {
				prepared = PrepareForIssuingCmds();
				if (!prepared) {
					printf("Error: failed to prepare.\n");
				}
			}

			while (1) {
				bool issued = IssueCmds(NULL);
				if (!issued) {
					printf("Error: failed to issue.\n");
					break;
				}

				usleep(10);
			}

			usleep(10);
		}
	} else {
		bool prepared = PrepareForIssuingCmds();
		if (prepared) {
			bool issued = IssueCmds(argv[1]);
			if (!issued) {
				printf("Error: failed to issue.\n");
			}
		} else {
			printf("Error: failed to prepare.\n");
		}
	}

	CloseConnection();
	CloseKillConnection();

	return 0;
}


